-----------Troll Sports Math-----------
A 4am and san inc crack      2019-05-27
---------------------------------------

Name: Troll Sports Math: Math Word
  Problems for Grades 4-6
Genre: educational
Year: 1991
Publisher: Troll Associates
Platform: Apple ][+ or later
Media: 3.5-inch disk
Sides: 1
OS: UniDOS
Previous cracks: none

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


Copy ][+ 9.1 ("COPY" > "DISK")
  no read errors, but copy loads DOS
  then fills the screen with garbage
  (well, a repeating pattern, so more
  like structured garbage) and reboots

CFFA 3000 import
  no read errors, but booting the disk
  image in an emulator exhibits the
  same behavior as the backup I made
  on real hardware with Copy ][+

I do not know if the original disk
boots. The slim amount of documentation
I received with the disk states that it
requires a UniDisk 3.5 drive, which I
do not have. However, legitimate disk
read failures do not tend to fill the
screen with structured garbage, so I'm
guessing this is the failure mode of a
run-time protection check.

Next steps:

  1. Trace the startup program
  2. Find and disable the protection
     check
  3. Declare victory(*)

(*) go to the gym

                   ~

               Chapter 1
   In Which Things Quickly Get Hairy


Unlike most 3.5-inch disks, this does
not boot ProDOS. Quick investigation
reveals it runs some sort of DOS-on-
800K-disk thing called "UniDOS." I
found a "UniDOS master" online that
boots to a working DOS prompt.

[S5,D1=UniDOS master]
[S5,D2=non-working copy]

]PR#5

]CATALOG,S5,D2

DISK VOLUME 001

 A 056 HELLO
 B 006 T0
 B 022 M
 B 014 M0
 B 026 A
 B 010 A0
 B 024 U
 B 010 A22
 B 008 A21
 B 010 A12
 B 008 A11
 B 010 A32
 B 008 A31
 B 010 B0
 B 026 B
 B 010 B12
 B 008 B21
 B 010 B22
 B 008 B31
 B 010 B32
 B 008 B11
 B 010 C0
 B 008 C11
 B 026 C
 B 008 C21
 B 008 C31
 B 010 C22
 B 010 C12
 B 010 C32
 B 014 M2

 1109 FREE SECTORS

That's... a lot of free sectors.

I'm going to assume that the startup
program is HELLO, like a 5.25-inch disk
running DOS 3.3.

]LOAD HELLO
]LIST

 10  CALL 14386

Oh great, the "BASIC" program is really
just a shell for an assembly language
program contained within it.

]CALL -151

*3832L

; set up, then call, the memory move
; routine in ROM
; copying $3857 -> $3FEA
3832-   A9 57       LDA   #$57
3834-   85 3C       STA   $3C
3836-   A9 38       LDA   #$38
3838-   85 3D       STA   $3D
383A-   A9 6D       LDA   #$6D
383C-   85 3E       STA   $3E
383E-   A9 3D       LDA   #$3D
3840-   85 3F       STA   $3F
3842-   A9 EA       LDA   #$EA
3844-   85 42       STA   $42
3846-   A9 3F       LDA   #$3F
3848-   85 43       STA   $43
384A-   A0 00       LDY   #$00
384C-   20 2C FE    JSR   $FE2C

; call the code we just moved
384F-   20 EA 3F    JSR   $3FEA

Let's temporarily put an "RTS" there
and let it call the memory move.

*384F:60

*3832G

*3FEAL

; check various machine identification
; bytes in ROM
3FEA-   AD B3 FB    LDA   $FBB3
3FED-   C9 06       CMP   #$06
3FEF-   D0 0F       BNE   $4000
3FF1-   AD C0 FB    LDA   $FBC0
3FF4-   C9 E0       CMP   #$E0
3FF6-   D0 08       BNE   $4000
3FF8-   AD DD FB    LDA   $FBDD
3FFB-   C9 02       CMP   #$02
3FFD-   D0 01       BNE   $4000
3FFF-   60          RTS
4000-   6C 2B 44    JMP   ($442B)

I'm not sure that these tests ever do
anything but branch to $4000. On my
enhanced Apple //e, $FBDD is #$A9, so
we end up at $4000.

*442B.442C

442B- CA 43

*43CAL

; save flags
43CA-   08          PHP

; check for Apple IIgs
43CB-   D8          CLD
43CC-   38          SEC
43CD-   20 1F FE    JSR   $FE1F

; if carry is set, it's not a IIgs
43D0-   B0 02       BCS   $43D4
43D2-   E2          ???

; IIgs path
43D3-   30 A5       BMI   $437A

The underlying code is similar on 8-bit
and 16-bit machines. I'll only step
through the 8-bit path, which starts
at $43D4.

*43D4L

; save some zero page values
43D4-   A5 06       LDA   $06
43D6-   8D C7 43    STA   $43C7
43D9-   A5 07       LDA   $07
43DB-   8D C8 43    STA   $43C8
43DE-   A0 00       LDY   #$00
43E0-   84 06       STY   $06

; always branches
43E2-   F0 01       BEQ   $43E5
[43E4-   AF          ???]
43E5-   A0 03       LDY   #$03
43E7-   A9 40       LDA   #$40

; always branches
43E9-   D0 01       BNE   $43EC
43EB-   5C          ???
43EC-   85 07       STA   $07

; always branches
43EE-   D0 01       BNE   $43F1
[43F0-   22          ???]
43F1-   20 24 44    JSR   $4424

*4424L

; get a byte and "decrypt" it with EOR
4424-   B1 06       LDA   ($06),Y
4426-   49 E1       EOR   #$E1
4428-   38          SEC
4429-   60          RTS

; always branches, because the routine
; at $4424 explicitly sets the carry

43F4-   B0 01       BCS   $43F7
[43F6-   43          ???]

; store the decrypted byte
43F7-   91 06       STA   ($06),Y
43F9-   C8          INY
43FA-   D0 F5       BNE   $43F1

All of that for a lightly obfuscated
decryption loop of a page of memory at
$4000.

; always branches
43FC-   F0 01       BEQ   $43FF
[43FE-   C7          ???]

; oh look, we're decrypting more pages
43FF-   E6 07       INC   $07

; always branches
4401-   D0 01       BNE   $4404
[4403-   22          ???]
4404-   A5 07       LDA   $07

; up to $4300
4406-   C9 43       CMP   #$43

; loop back
4408-   D0 E7       BNE   $43F1

; always branches, because if it was
; not equal we already branched back
440A-   F0 01       BEQ   $440D
;440C-   13          ???

; another decryption loop, for part of
; the page at $4300
440D-   20 24 44    JSR   $4424
4410-   B0 01       BCS   $4413
4412-   27          ???
4413-   91 06       STA   ($06),Y
4415-   C8          INY

; up to $43C6
4416-   C0 C7       CPY   #$C7

; loop back
4418-   D0 F3       BNE   $440D

; always branches
441A-   F0 01       BEQ   $441D
[441C-   3C          ???]
441D-   6C 21 44    JMP   ($4421)

*4421.4422

4421- 03 40

That's in the code we just decrypted,
so let's do that.

; RTS instead of JMP
*441D:60

; execute the decryption loops without
; the initial PHP
*43CBG

Piece of cake.

                   ~

               Chapter 2
  In Which It Is Most Definitely Not
 A Piece Of Cake And The Author Would
    Appreciate It If He Would Stop
            Calling It That


Let's see what wonderous code awaits us
after all that obfuscation, decryption,
and indirection.

*4003L

; munge reset vector, so you know stuff
; is getting serious
4003-   A0 00       LDY   #$00
4005-   8C F4 03    STY   $03F4

; I checked and this is the same as it
; is under DOS 3.3 -- the boot slot x16
4008-   AD E9 B7    LDA   $B7E9
400B-   4A          LSR
400C-   4A          LSR
400D-   4A          LSR
400E-   4A          LSR
400F-   09 C0       ORA   #$C0
4011-   8D 19 40    STA   $4019
4014-   8D 61 40    STA   $4061

; self-modified above by the boot slot
4017-   AD FF C5    LDA   $C5FF
401A-   18          CLC
401B-   69 03       ADC   #$03
401D-   8D 60 40    STA   $4060

; drive (1 or 2)
4020-   AD EA B7    LDA   $B7EA
4023-   C9 01       CMP   #$01
4025-   F0 17       BEQ   $403E

; for reasons passing all understanding
; this protection check supports
; booting from drive 2
4027-   A9 02       LDA   #$02
4029-   8D 0E 41    STA   $410E
402C-   8D 15 41    STA   $4115
402F-   8D 1C 41    STA   $411C
4032-   8D 27 41    STA   $4127
4035-   8D 2F 41    STA   $412F
4038-   8D 34 41    STA   $4134
403B-   8D 40 41    STA   $4140

; check for IIgs again
403E-   38          SEC
403F-   20 1F FE    JSR   $FE1F
4042-   90 1E       BCC   $4062

; fall through for 8-bit machine, and
; once again check a bunch of different
; machine identification bytes
4044-   AD B3 FB    LDA   $FBB3
4047-   C9 06       CMP   #$06
4049-   D0 11       BNE   $405C
404B-   AD C0 FB    LDA   $FBC0
404E-   C9 00       CMP   #$00
4050-   D0 0A       BNE   $405C
4052-   AD BF FB    LDA   $FBBF
4055-   C9 05       CMP   #$05
4057-   D0 03       BNE   $405C

; if hardware is unsupported, skip the
; protection check (I'm not sure if any
; Apple II model ever gets here)
4059-   4C E8 40    JMP   $40E8

; hardware checks out, let's do this
405C-   4C 7A 40    JMP   $407A

*407AL

; call a routine that takes parameters
; on the stack, branch to $409B if it
; fails (more on this in a moment)
407A-   20 5F 40    JSR   $405F
407D-  [04]
407E-  [26 41]
4080-   B0 19       BCS   $409B

; again, but different parameters
4082-   20 5F 40    JSR   $405F
4085-  [04]
4086-  [2E 41]
4088-   B0 11       BCS   $409B

; again
408A-   20 5F 40    JSR   $405F
408D-  [04]
408E-  [33 41]
4090-   B0 09       BCS   $409B

; again
4092-   20 5F 40    JSR   $405F
4095-  [01]
4096-  [0D 41]

; all done
4098-   4C E8 40    JMP   $40E8

*40E8L

; copy this code to lower memory
40E8-   A0 17       LDY   #$17
40EA-   B9 F6 40    LDA   $40F6,Y
40ED-   99 00 02    STA   $0200,Y
40F0-   88          DEY
40F1-   10 F7       BPL   $40EA

; and execute it from there
40F3-   4C 00 02    JMP   $0200

; wipe the decrypted protection code
40F6-   A9 00       LDA   #$00
40F8-   A8          TAY
40F9-   99 03 40    STA   $4003,Y
40FC-   99 03 41    STA   $4103,Y
40FF-   99 03 42    STA   $4203,Y
4102-   99 03 43    STA   $4303,Y
4105-   99 03 44    STA   $4403,Y
4108-   88          DEY
4109-   D0 EE       BNE   $40F9

; restore flags (pushed at $43CA)
410B-   28          PLP

; return to caller with no further
; side effects
410C-   60          RTS

That's the success path. But if any of
the calls to $405F fail, we end up at
$409B, which seems bad:

; The Badlands
409B-   A9 00       LDA   #$00
409D-   8D C9 40    STA   $40C9
40A0-   A9 C5       LDA   #$C5
40A2-   8D CA 40    STA   $40CA

; copy to lower memory
40A5-   A0 18       LDY   #$18
40A7-   B9 B3 40    LDA   $40B3,Y
40AA-   99 00 02    STA   $0200,Y
40AD-   88          DEY
40AE-   10 F7       BPL   $40A7

; and continue from there
40B0-   4C 00 02    JMP   $0200

; wipe main memory
40B3-   A0 00       LDY   #$00
40B5-   A9 03       LDA   #$03
40B7-   84 06       STY   $06
40B9-   85 07       STA   $07
40BB-   91 06       STA   ($06),Y
40BD-   C8          INY
40BE-   D0 FB       BNE   $40BB
40C0-   E6 07       INC   $07
40C2-   A5 07       LDA   $07
40C4-   C9 C0       CMP   #$C0
40C6-   D0 F3       BNE   $40BB

; reboot (this was self-modified at
; $409B to always jump to $C500 --
; ironically this is NOT based on the
; boot slot, because I guess filthy
; pirates don't deserve that kind of
; consideration)
40C8-   4C C8 40    JMP   $40C8

So we're doing a thing at $405F, four
times but with different parameters,
and if they all work, we clean up and
return to the caller as if nothing
happened.

Let's see what we're doing at $405F.

*405FL

405F-   4C 0A C5    JMP   $C50A

Oh, great. We're calling the SmartPort
firmware.

                   ~

               Chapter 3
      In Which Everyone Is Smart
           In Their Own Way


SmartPort firmware is documented in
"Apple IIgs Firmware Reference," ch. 7.

                 --v--

This is an example of a standard
SmartPort call:

; Call SmartPort command dispatcher
SP_CALL   JSR  DISPATCH

; This specifies the command type
          DFB  CMDNUM

; word pointer to the parameter list
          DW   CMDLIST

; carry is set on an error
          BCS  ERROR

                 --^--

That's exactly what we're doing at
$407A -- calling the command dispatch
(adjusted for the boot slot). The next
three bytes are a command number and
the address of a parameter block. Then
we branch to The Badlands at $409B on
error.

407A-   20 5F 40    JSR   $405F
407D-  [04]
407E-  [26 41]
4080-   B0 19       BCS   $409B

In this first call, we're issuing the
SmartPort command #$04 with a parameter
block at $4126. This is a "control
call" -- an extension mechanism to send
commands that different devices can
interpret in different ways. The
control calls for the UniDisk 3.5 are
documented later in chapter 7.

(This, by the way, explains why the
program "requires" a UniDisk 3.5 drive.
It's not the program that requires it,
it's the copy protection.)

The parameter block at $4126 gives the
details on this "control call."

*4126.

4126- .. .. .. .. .. .. 03 01
4128- 2B 41 06 02 00 05 03 01

Taking this one byte at a time:

$4126: $03   parameter count
$4127: $01   unit number (possibly self-
             modified above to support
             running from drive 2)
$4128: $412B address of "control list,"
             i.e. the parameter block
             for this custom call
$412A: $06   control code: "SetAddress"
$412B: $0305 address within the UniDisk
             drive

The firmware reference manual describes
the "SetAddress" control call:

                 --v--

This call is used to set the address in
the UniDisk 3.5 controller's memory
space that the Download call will load
a 65C02 routine into. Care must be
taken that the download address is set
only to free space in the UniDisk 3.5
memory map.

                 --^--

So we're setting up an environment to
transfer executable code to the drive
itself. Because that's not completely
insane. (Yes, I'm aware that this was
common on other platforms like the
Commodore 64. That doesn't make it any
less insane.)

So what are we downloading? That's the
next call, at $4082:

4082-   20 5F 40    JSR   $405F
4085-  [04]
4086-  [2E 41]
4088-   B0 11       BCS   $409B

Same deal, we're calling the SmartPort
firmware with a control call. $4085 is
#$04, a control call command. $4086 is
$412E, the address of the parameter
block. And we branch to The Badlands on
error.

Looking at the parameter block:

*412E.

412E- .. .. .. .. .. .. 03 01
4130- 00 42 07

We see a similar structure as the first
call, but with a different control code
(at $4132):

$412E: $03   parameter count
$412F: $01   unit number (possibly self-
             modified above to support
             running from drive 2)
$4130: $4200 address of parameter block
$4132: $07   control code: "Download"

This is what the firmware reference
manual has to say about the "Download"
call:

                 --v--

This call is used to download an
executable 65C02 routine into the
memory resident on the UniDisk 3.5
controller. The address that the
routine is loaded into is set by the
SetAddress call. The count field must
be set to the length of the 65C02
routine to be downloaded.

                 --^--

So $4130 points to $4200, which will
contain a length word followed by the
actual code to download to the drive.
The code is 65c02 code, so I can use
the monitor disassembly to read it.

*4200.

4200- 70 00

#$70 bytes of code, starting at $4202.
But it will be executed on the drive
itself, at address $0305.

We'll look at it in a moment. First,
for completeness, I want to note that
this "Download" control call does not
auto-execute the code it transfers to
the drive. That happens in the next
call, at $408A:

408A-   20 5F 40    JSR   $405F
408D-  [04]
408E-  [33 41]
4090-   B0 09       BCS   $409B

$408D is #$04, so we are once again
doing a control call. $408E points to
the parameter block at $4133. We jump
to The Badlands if there's any error.

*4133.

4133- .. .. .. 03 01 38 41 05

$4133: $03   parameter count
$4134: $01   unit number (possibly self-
             modified above to support
             running from drive 2)
$4135: $4138 address of parameter block
$4137: $05   control code: "Execute"

Again from the fine manual:

                 --v--

This call is used to dispatch the
intelligent controller in the UniDisk
3.5 device to execute a 65C02
subroutine. The register setup is
passed to the routine to be executed
from the control list.

                 --^--

The parameter block at $4138 gives the
initial values of each register. (This
is a 65c02, so it has A, X, and Y
registers just like the Apple II its
connected to.)

*4138.

4138- 06 00 05 05 34 00 05 03

$4138: $0006 block size
$413A: $05   A value
$413B: $05   X value
$413C: $34   Y value
$413D: $00   flags value (like PHP/PLP)
$413E: $0305 address of code to execute

Unsurprisingly, we are executing the
code we just downloaded to $0305.

Now let's look at that code.

                   ~

               Chapter 4
   In Which Murphy's Law Never Fails


Taking advantage of the fact that this
floppy drive runs the same processor as
the host Apple II (I AM STILL NOT OVER
THAT, BY THE WAY), we can manually move
the code to $0305 on the Apple II and
use the monitor disassembly to list it.

*305<4202.4271M

*305L

; save some zero page addresses
0305-   A5 73       LDA   $73
0307-   8D 4C 05    STA   $054C
030A-   A5 74       LDA   $74
030C-   8D 4D 05    STA   $054D

; copy some zero page values
030F-   A0 05       LDY   #$05
0311-   B1 73       LDA   ($73),Y
0313-   99 20 05    STA   $0520,Y
0316-   88          DEY
0317-   D0 F8       BNE   $0311

; overwrite some zero page addresses
0319-   A9 4C       LDA   #$4C
031B-   85 75       STA   $75
031D-   A9 20       LDA   #$20
031F-   85 73       STA   $73
0321-   A9 05       LDA   #$05
0323-   85 74       STA   $74

; call... something in ROM
0325-   20 6A E5    JSR   $E56A
0328-   20 62 E1    JSR   $E162
032B-   EA          NOP

; get a raw nibble from the disk (like
; "LDA $C08C,X" on a 5.25-inch floppy)
032C-   AD 0E 0A    LDA   $0A0E
032F-   10 FB       BPL   $032C

; loop until we find a $D5 nibble
0331-   C9 D5       CMP   #$D5
0333-   D0 F7       BNE   $032C

; burn CPU cycles
0335-   48          PHA
0336-   68          PLA

; next nibble must be $B5 (nonstandard)
0337-   AD 0E 0A    LDA   $0A0E
033A-   10 FB       BPL   $0337
033C-   C9 B5       CMP   #$B5

; otherwise loop back to find another
; $D5 nibble
033E-   D0 EC       BNE   $032C

; restore RdAddr hooks in zero page
0340-   AD 4C 05    LDA   $054C
0343-   85 73       STA   $73
0345-   AD 4D 05    LDA   $054D
0348-   85 74       STA   $74
034A-   38          SEC
034B-   60          RTS

According to the fine manual, zero page
$73 and $74 are part of a "hook table"
that jumps to all the hookable routines
the drive supports. This includes
routines like "RdAddr: find and decode
an address field" ($73/$74), "ReadData:
find and load a data field in RAM"
($76/$77), and so on.

So we're setting the "RdAddr" hook to
point to $0520. Well, we just copied 5
bytes of code from ($73) to $0521 (at
$030F), but nothing to address $0520.
The fine manual lists $0500..$05FF as
"free space," so I'm confused. What
exactly is supposed to be at $0520?

The answer is... a bit shocking. This
entire copy protection routine was set
up incorrectly. We're not supposed to
be downloading code to the address
$0305 inside the floppy drive. No,
really, we're not supposed to do
that. The fine manual says that's part
of a buffer for "host communication
(format sector buffer I)." I'm not sure
what that is, but it is definitely not
"free space." The $0500..$05FF page is
explicitly listed as "free space."
$0305 is not.

The root cause, I believe, is an off-
by-1 bug in the parameter blocks of the
original control calls. This code isn't
supposed to be downloaded and executed
at $0305; it's supposed to be at $0500.
That makes more sense on its face, just
because that's the largest block of
free space in the drive's memory map.
But also, it would make the code itself
make more sense.

Observe.

Suppose, for the sake of argument, that
this code ended up at $0500 instead of
$0305. Then it would look like this:

0500-   A5 73       LDA   $73
0502-   8D 4C 05    STA   $054C
0505-   A5 74       LDA   $74
0507-   8D 4D 05    STA   $054D
050A-   A0 05       LDY   #$05
050C-   B1 73       LDA   ($73),Y
050E-   99 20 05    STA   $0520,Y
0511-   88          DEY
0512-   D0 F8       BNE   $050C
0514-   A9 4C       LDA   #$4C
0516-   85 75       STA   $75
0518-   A9 20       LDA   #$20
051A-   85 73       STA   $73
051C-   A9 05       LDA   #$05
051E-   85 74       STA   $74

; now these two JSR calls are self-
; modified by the code above
0520-   20 6A E5    JSR   $E56A
0523-   20 62 E1    JSR   $E162
0526-   EA          NOP
0527-   AD 0E 0A    LDA   $0A0E
052A-   10 FB       BPL   $0527
052C-   C9 D5       CMP   #$D5
052E-   D0 F7       BNE   $0527
0530-   48          PHA
0531-   68          PLA
0532-   AD 0E 0A    LDA   $0A0E
0535-   10 FB       BPL   $0532
0537-   C9 B5       CMP   #$B5
0539-   D0 EC       BNE   $0527
053B-   AD 4C 05    LDA   $054C
053E-   85 73       STA   $73
0540-   AD 4D 05    LDA   $054D
0543-   85 74       STA   $74
0545-   38          SEC
0546-   60          RTS

If this had been downloaded to, and
executed from, address $0500, the
RdAddr hook at $73/$74 would have been
redirected to $0520. The code at $520
would have been self-modified to be the
first 5 bytes of code from the original
RdAddr routine, followed by the custom
code starting at $0527. That custom
code would check for a nonstandard
nibble sequence on the next disk read,
looping forever if it couldn't find it.
(The drive has a "watchdog" timeout, so
this would eventually just time out and
return an error to the host Apple II.)
If it succeeded in finding the custom
nibble sequence, it would have restored
the RdAddr hook at $73/$74 before
returning.

All that, combined with the final
SmartPort call at $4092...

4092-   20 5F 40    JSR   $405F
4095-  [01]
4096-  [0D 41]

Command #$01 is a "read" command. So we
would have the drive read a block from
the disk, but with the hooked RdAddr
routine that points to the protection
code at $0520.

*410D.

410D- .. .. .. .. .. 03 01 00
4110- 02 10 00 00

$410D: $03   parameter count
$410E: $01   unit number (possibly self-
             modified above to support
             running from drive 2)
$410F: $0200 address of read buffer (in
             the drive memory)
$4111: $000010 block number

And *that* SmartPort call would succeed
or fail based on whether it found the
custom nibble sequence ($D5 $B5) while
attempting to read block $10.

And *that* is the entire protection: a
single custom nibble near block $10.

Except none of that happened, because
there was an off-by-1 bug in the
parameter block of the control call, so
the code that self-modified as if were
at $0500 ended up being at $0305
instead, and I HAVE LITERALLY NO IDEA
HOW THIS EVER WORKED AT ALL, even on an
original disk with a compatible UniDisk
3.5 drive.

The RdAddr hook ($73/$74) ends up
pointing to uninitialized memory at
$0520, but because the JSRs at $0325
are never self-modified, it falls
through to the actual protection code
to check for a custom nibble sequence,
but during the Execute call instead of
the final ReadBlock call, then restores
the RdAddr hook before returning. So
the ReadBlock call will always succeed,
because by the time it happens, the
RdAddr hook has been restored to its
original value.

I think.

Anyway, the entire thing is f---ed.
Copy protection barely works in the
best of circumstances, which these are
not. Downloading code to peripherals is
insane in the best of circumstances,
which these are not. I will die on this
hill.(*)

(*) not guaranteed, actual death may
    vary

The protection routine has no side
effects. We can simply disable the "JSR
$3FEA" (at $384F) and it will happily
continue the boot.

Using Glen Bredon's "Block Warden," I
can search the disk for the offending
JSR. The syntax always confuses me, so
for future me, the correct sequence is

  [C]hange device to "Slot 7, Drive 2"
  [E]dit
  [Ctrl-S] and enter "$20EA3F" as the
           search string to search for
           a hex sequence

; change "JSR" opcode to "BIT"
Block $002F, byte $50: 20 -> 2C

As an added bonus, we're removed the
only thing that tied us to the UniDisk
3.5, so I can boot my cracked copy on
my Apple SuperDrive. It's a lovely
game. Shame about the protection.

Quod erat liberandum.

                   ~

           Acknowledgements


Many thanks to qkumba for patiently
explaining to me how SmartPort firmware
calls work, so I could explain it to
you.

---------------------------------------
A 4am and san inc crack        No. 2032
------------------EOF------------------
